{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Example 2: Intermediate Document Summary with Human Editing\n",
    "\n",
    "## Problem Statement\n",
    "Create a LangGraph that processes a document to generate a summary, allows a human to edit the summary for accuracy and completeness, and then uses the human-edited summary for a follow-up task (like generating action items or creating a presentation outline). This demonstrates more sophisticated human-in-the-loop interaction where human input directly improves the AI's work product.\n",
    "\n",
    "## Features\n",
    "- Document processing and summarization\n",
    "- Human editing interface for summary refinement\n",
    "- Follow-up task generation using edited summary\n",
    "- Version tracking and comparison\n",
    "- Error handling and input validation"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Install required packages (run this cell if packages are not installed)\n",
    "# !pip install langchain langgraph langchain-openai pytest difflib"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "from typing import TypedDict, Annotated, List, Dict, Optional\n",
    "from langchain_core.messages import HumanMessage, AIMessage, SystemMessage\n",
    "from langchain_openai import ChatOpenAI\n",
    "from langgraph.graph import StateGraph, END\n",
    "from langgraph.graph.message import add_messages\n",
    "import json\n",
    "from datetime import datetime\n",
    "import difflib\n",
    "import re\n",
    "\n",
    "# Set up OpenAI API key (make sure this is set in your environment)\n",
    "# os.environ[\"OPENAI_API_KEY\"] = \"your-api-key-here\"\n",
    "\n",
    "print(\"📚 Document Summary Editing with Human-in-the-Loop\")\n",
    "print(\"=\" * 55)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 1: Define the State\n",
    "The state tracks the document, AI-generated summary, human-edited version, and follow-up tasks."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class DocumentSummaryState(TypedDict):\n",
    "    messages: Annotated[List, add_messages]\n",
    "    original_document: str\n",
    "    ai_summary: str\n",
    "    human_edited_summary: str\n",
    "    summary_approved: bool\n",
    "    editing_notes: str\n",
    "    follow_up_task: str\n",
    "    follow_up_result: str\n",
    "    version_history: List[Dict]\n",
    "    processing_stage: str\n",
    "\n",
    "print(\"✅ State defined with fields:\")\n",
    "print(\"   - messages: conversation history\")\n",
    "print(\"   - original_document: source document text\")\n",
    "print(\"   - ai_summary: AI-generated summary\")\n",
    "print(\"   - human_edited_summary: human-refined summary\")\n",
    "print(\"   - summary_approved: human approval status\")\n",
    "print(\"   - editing_notes: human feedback and notes\")\n",
    "print(\"   - follow_up_task: task to perform with edited summary\")\n",
    "print(\"   - follow_up_result: result of follow-up task\")\n",
    "print(\"   - version_history: track summary versions\")\n",
    "print(\"   - processing_stage: current workflow stage\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 2: Helper Functions\n",
    "Utility functions for document processing and comparison."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def create_version_entry(summary: str, editor: str, notes: str = \"\") -> Dict:\n",
    "    \"\"\"Create a version history entry.\"\"\"\n",
    "    return {\n",
    "        \"timestamp\": datetime.now().isoformat(),\n",
    "        \"summary\": summary,\n",
    "        \"editor\": editor,\n",
    "        \"notes\": notes,\n",
    "        \"word_count\": len(summary.split())\n",
    "    }\n",
    "\n",
    "def show_diff(original: str, edited: str) -> str:\n",
    "    \"\"\"Show differences between original and edited text.\"\"\"\n",
    "    diff = list(difflib.unified_diff(\n",
    "        original.splitlines(keepends=True),\n",
    "        edited.splitlines(keepends=True),\n",
    "        fromfile='AI Summary',\n",
    "        tofile='Human Edited',\n",
    "        lineterm=''\n",
    "    ))\n",
    "    return ''.join(diff) if diff else \"No changes detected.\"\n",
    "\n",
    "def validate_document(document: str) -> tuple[bool, str]:\n",
    "    \"\"\"Validate document format and content.\"\"\"\n",
    "    if not document or not document.strip():\n",
    "        return False, \"Document is empty\"\n",
    "    \n",
    "    word_count = len(document.split())\n",
    "    if word_count < 10:\n",
    "        return False, f\"Document too short ({word_count} words). Need at least 10 words.\"\n",
    "    \n",
    "    if word_count > 5000:\n",
    "        return False, f\"Document too long ({word_count} words). Maximum 5000 words.\"\n",
    "    \n",
    "    return True, f\"Document validated ({word_count} words)\"\n",
    "\n",
    "print(\"✅ Helper functions defined: create_version_entry, show_diff, validate_document\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 3: Define the Graph Nodes\n",
    "Each node represents a step in the document processing and editing workflow."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def process_document(state: DocumentSummaryState) -> DocumentSummaryState:\n",
    "    \"\"\"Process and validate the input document.\"\"\"\n",
    "    print(\"📄 Processing document...\")\n",
    "    \n",
    "    try:\n",
    "        messages = state[\"messages\"]\n",
    "        if not messages:\n",
    "            raise ValueError(\"No messages found\")\n",
    "        \n",
    "        # Extract document from the first message\n",
    "        document = messages[0].content\n",
    "        \n",
    "        # Validate document\n",
    "        is_valid, validation_msg = validate_document(document)\n",
    "        print(f\"📋 Validation: {validation_msg}\")\n",
    "        \n",
    "        if not is_valid:\n",
    "            raise ValueError(validation_msg)\n",
    "        \n",
    "        return {\n",
    "            \"original_document\": document,\n",
    "            \"processing_stage\": \"document_processed\",\n",
    "            \"version_history\": []\n",
    "        }\n",
    "        \n",
    "    except Exception as e:\n",
    "        print(f\"❌ Error processing document: {e}\")\n",
    "        return {\n",
    "            \"original_document\": f\"Error: {str(e)}\",\n",
    "            \"processing_stage\": \"error\"\n",
    "        }\n",
    "\n",
    "\n",
    "def generate_ai_summary(state: DocumentSummaryState) -> DocumentSummaryState:\n",
    "    \"\"\"Generate AI summary of the document.\"\"\"\n",
    "    print(\"🤖 Generating AI summary...\")\n",
    "    \n",
    "    try:\n",
    "        document = state[\"original_document\"]\n",
    "        \n",
    "        if document.startswith(\"Error:\"):\n",
    "            return {\"ai_summary\": \"Cannot summarize due to document processing error.\"}\n",
    "        \n",
    "        # Create summarization prompt\n",
    "        system_msg = SystemMessage(content=\"\"\"You are an expert document summarizer. \n",
    "        Create a clear, concise summary that captures the key points, main arguments, \n",
    "        and important details. Structure your summary with bullet points or paragraphs \n",
    "        as appropriate. Aim for 150-300 words depending on document length.\"\"\")\n",
    "        \n",
    "        user_msg = HumanMessage(content=f\"Please summarize this document:\\n\\n{document}\")\n",
    "        \n",
    "        llm = ChatOpenAI(model=\"gpt-4\", temperature=0.3)\n",
    "        response = llm.invoke([system_msg, user_msg])\n",
    "        \n",
    "        ai_summary = response.content\n",
    "        print(f\"📝 AI summary generated ({len(ai_summary.split())} words)\")\n",
    "        \n",
    "        # Create version history entry\n",
    "        version_entry = create_version_entry(\n",
    "            ai_summary, \n",
    "            \"AI\", \n",
    "            \"Initial AI-generated summary\"\n",
    "        )\n",
    "        \n",
    "        return {\n",
    "            \"ai_summary\": ai_summary,\n",
    "            \"processing_stage\": \"summary_generated\",\n",
    "            \"version_history\": state.get(\"version_history\", []) + [version_entry],\n",
    "            \"messages\": [response]\n",
    "        }\n",
    "        \n",
    "    except Exception as e:\n",
    "        print(f\"❌ Error generating summary: {e}\")\n",
    "        return {\n",
    "            \"ai_summary\": f\"Error generating summary: {str(e)}\",\n",
    "            \"processing_stage\": \"error\"\n",
    "        }\n",
    "\n",
    "\n",
    "def request_human_editing(state: DocumentSummaryState) -> DocumentSummaryState:\n",
    "    \"\"\"Request human editing of the AI summary.\"\"\"\n",
    "    print(\"👤 Requesting human editing...\")\n",
    "    \n",
    "    try:\n",
    "        ai_summary = state[\"ai_summary\"]\n",
    "        \n",
    "        if ai_summary.startswith(\"Error\"):\n",
    "            return {\n",
    "                \"summary_approved\": False,\n",
    "                \"editing_notes\": \"Cannot edit due to summary generation error\"\n",
    "            }\n",
    "        \n",
    "        print(f\"\\n{'='*70}\")\n",
    "        print(\"📝 SUMMARY EDITING INTERFACE\")\n",
    "        print(f\"{'='*70}\")\n",
    "        print(\"\\n🤖 AI-Generated Summary:\")\n",
    "        print(\"-\" * 40)\n",
    "        print(ai_summary)\n",
    "        print(\"-\" * 40)\n",
    "        \n",
    "        print(\"\\n📋 Options:\")\n",
    "        print(\"1. Type 'approve' to accept the summary as-is\")\n",
    "        print(\"2. Type 'edit' to make changes\")\n",
    "        print(\"3. Type 'regenerate' to ask AI to try again\")\n",
    "        \n",
    "        while True:\n",
    "            action = input(\"\\nWhat would you like to do? (approve/edit/regenerate): \").strip().lower()\n",
    "            \n",
    "            if action == 'approve':\n",
    "                print(\"✅ Summary approved without changes\")\n",
    "                return {\n",
    "                    \"human_edited_summary\": ai_summary,\n",
    "                    \"summary_approved\": True,\n",
    "                    \"editing_notes\": \"Approved without changes\",\n",
    "                    \"processing_stage\": \"summary_approved\"\n",
    "                }\n",
    "            \n",
    "            elif action == 'edit':\n",
    "                print(\"\\n✏️ Edit Mode\")\n",
    "                print(\"Instructions: Copy the summary above and make your changes below.\")\n",
    "                print(\"Type 'DONE' on a new line when finished.\\n\")\n",
    "                \n",
    "                edited_lines = []\n",
    "                print(\"Enter your edited summary:\")\n",
    "                \n",
    "                while True:\n",
    "                    line = input()\n",
    "                    if line.strip().upper() == 'DONE':\n",
    "                        break\n",
    "                    edited_lines.append(line)\n",
    "                \n",
    "                edited_summary = '\\n'.join(edited_lines).strip()\n",
    "                \n",
    "                if not edited_summary:\n",
    "                    print(\"❌ Empty summary. Please try again.\")\n",
    "                    continue\n",
    "                \n",
    "                # Get editing notes\n",
    "                notes = input(\"\\nOptional: Add notes about your changes: \").strip()\n",
    "                \n",
    "                # Show diff\n",
    "                print(\"\\n🔍 Changes made:\")\n",
    "                diff = show_diff(ai_summary, edited_summary)\n",
    "                if diff != \"No changes detected.\":\n",
    "                    print(diff)\n",
    "                else:\n",
    "                    print(\"No changes detected.\")\n",
    "                \n",
    "                # Create version entry\n",
    "                version_entry = create_version_entry(\n",
    "                    edited_summary,\n",
    "                    \"Human\",\n",
    "                    notes or \"Human edited version\"\n",
    "                )\n",
    "                \n",
    "                print(\"✅ Summary edited successfully\")\n",
    "                return {\n",
    "                    \"human_edited_summary\": edited_summary,\n",
    "                    \"summary_approved\": True,\n",
    "                    \"editing_notes\": notes or \"Human edited version\",\n",
    "                    \"processing_stage\": \"summary_approved\",\n",
    "                    \"version_history\": state.get(\"version_history\", []) + [version_entry]\n",
    "                }\n",
    "            \n",
    "            elif action == 'regenerate':\n",
    "                feedback = input(\"What should be improved in the summary? \").strip()\n",
    "                print(\"🔄 Requesting regeneration with feedback\")\n",
    "                return {\n",
    "                    \"summary_approved\": False,\n",
    "                    \"editing_notes\": feedback,\n",
    "                    \"processing_stage\": \"regenerate_requested\"\n",
    "                }\n",
    "            \n",
    "            else:\n",
    "                print(\"Please enter 'approve', 'edit', or 'regenerate'\")\n",
    "        \n",
    "    except Exception as e:\n",
    "        print(f\"❌ Error in editing interface: {e}\")\n",
    "        return {\n",
    "            \"summary_approved\": False,\n",
    "            \"editing_notes\": f\"Error in editing: {str(e)}\",\n",
    "            \"processing_stage\": \"error\"\n",
    "        }\n",
    "\n",
    "\n",
    "def regenerate_summary(state: DocumentSummaryState) -> DocumentSummaryState:\n",
    "    \"\"\"Regenerate summary based on human feedback.\"\"\"\n",
    "    print(\"🔄 Regenerating summary with feedback...\")\n",
    "    \n",
    "    try:\n",
    "        document = state[\"original_document\"]\n",
    "        feedback = state[\"editing_notes\"]\n",
    "        \n",
    "        system_msg = SystemMessage(content=f\"\"\"You are an expert document summarizer. \n",
    "        The user has provided feedback on your previous summary. Please create an improved summary \n",
    "        that addresses their feedback.\n",
    "        \n",
    "        User feedback: {feedback}\n",
    "        \n",
    "        Create a clear, concise summary that incorporates this feedback while maintaining \n",
    "        professional quality and completeness.\"\"\")\n",
    "        \n",
    "        user_msg = HumanMessage(content=f\"Please summarize this document based on the feedback:\\n\\n{document}\")\n",
    "        \n",
    "        llm = ChatOpenAI(model=\"gpt-4\", temperature=0.3)\n",
    "        response = llm.invoke([system_msg, user_msg])\n",
    "        \n",
    "        new_summary = response.content\n",
    "        print(f\"📝 Regenerated summary ({len(new_summary.split())} words)\")\n",
    "        \n",
    "        # Create version entry\n",
    "        version_entry = create_version_entry(\n",
    "            new_summary,\n",
    "            \"AI\",\n",
    "            f\"Regenerated based on feedback: {feedback}\"\n",
    "        )\n",
    "        \n",
    "        return {\n",
    "            \"ai_summary\": new_summary,\n",
    "            \"processing_stage\": \"summary_generated\",\n",
    "            \"editing_notes\": \"\",  # Clear previous feedback\n",
    "            \"version_history\": state.get(\"version_history\", []) + [version_entry],\n",
    "            \"messages\": state.get(\"messages\", []) + [response]\n",
    "        }\n",
    "        \n",
    "    except Exception as e:\n",
    "        print(f\"❌ Error regenerating summary: {e}\")\n",
    "        return {\n",
    "            \"ai_summary\": f\"Error regenerating summary: {str(e)}\",\n",
    "            \"processing_stage\": \"error\"\n",
    "        }\n",
    "\n",
    "\n",
    "def execute_follow_up_task(state: DocumentSummaryState) -> DocumentSummaryState:\n",
    "    \"\"\"Execute follow-up task using the approved summary.\"\"\"\n",
    "    print(\"🎯 Executing follow-up task...\")\n",
    "    \n",
    "    try:\n",
    "        summary = state[\"human_edited_summary\"]\n",
    "        task = state.get(\"follow_up_task\", \"\")\n",
    "        \n",
    "        if not task:\n",
    "            # Ask user what follow-up task to perform\n",
    "            print(\"\\n📋 Follow-up Task Options:\")\n",
    "            print(\"1. Generate action items\")\n",
    "            print(\"2. Create presentation outline\")\n",
    "            print(\"3. Identify key stakeholders\")\n",
    "            print(\"4. Generate questions for discussion\")\n",
    "            print(\"5. Custom task\")\n",
    "            \n",
    "            while True:\n",
    "                choice = input(\"\\nSelect follow-up task (1-5): \").strip()\n",
    "                \n",
    "                if choice == '1':\n",
    "                    task = \"Generate a list of actionable items based on this summary\"\n",
    "                    break\n",
    "                elif choice == '2':\n",
    "                    task = \"Create a presentation outline based on this summary\"\n",
    "                    break\n",
    "                elif choice == '3':\n",
    "                    task = \"Identify key stakeholders mentioned or implied in this summary\"\n",
    "                    break\n",
    "                elif choice == '4':\n",
    "                    task = \"Generate thoughtful discussion questions based on this summary\"\n",
    "                    break\n",
    "                elif choice == '5':\n",
    "                    task = input(\"Enter your custom task: \").strip()\n",
    "                    if task:\n",
    "                        break\n",
    "                    else:\n",
    "                        print(\"Please enter a task description\")\n",
    "                        continue\n",
    "                else:\n",
    "                    print(\"Please enter 1, 2, 3, 4, or 5\")\n",
    "        \n",
    "        print(f\"🎯 Executing task: {task}\")\n",
    "        \n",
    "        # Execute the follow-up task\n",
    "        system_msg = SystemMessage(content=f\"\"\"You are a helpful assistant. \n",
    "        Based on the provided summary, please {task.lower()}. \n",
    "        Provide a well-structured, actionable response.\"\"\")\n",
    "        \n",
    "        user_msg = HumanMessage(content=f\"Summary:\\n{summary}\\n\\nTask: {task}\")\n",
    "        \n",
    "        llm = ChatOpenAI(model=\"gpt-4\", temperature=0.3)\n",
    "        response = llm.invoke([system_msg, user_msg])\n",
    "        \n",
    "        result = response.content\n",
    "        print(f\"✅ Follow-up task completed ({len(result.split())} words)\")\n",
    "        \n",
    "        return {\n",
    "            \"follow_up_task\": task,\n",
    "            \"follow_up_result\": result,\n",
    "            \"processing_stage\": \"completed\",\n",
    "            \"messages\": state.get(\"messages\", []) + [response]\n",
    "        }\n",
    "        \n",
    "    except Exception as e:\n",
    "        print(f\"❌ Error executing follow-up task: {e}\")\n",
    "        return {\n",
    "            \"follow_up_result\": f\"Error executing follow-up task: {str(e)}\",\n",
    "            \"processing_stage\": \"error\"\n",
    "        }\n",
    "\n",
    "print(\"✅ Nodes defined: process_document, generate_ai_summary, request_human_editing, regenerate_summary, execute_follow_up_task\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 4: Define Routing Logic\n",
    "The routing logic determines the flow through the graph based on editing decisions and processing status."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def route_after_editing(state: DocumentSummaryState) -> str:\n",
    "    \"\"\"Route based on editing decision.\"\"\"\n",
    "    stage = state.get(\"processing_stage\", \"\")\n",
    "    approved = state.get(\"summary_approved\", False)\n",
    "    \n",
    "    if stage == \"error\":\n",
    "        print(\"🔀 Routing to end due to error\")\n",
    "        return \"end\"\n",
    "    elif stage == \"regenerate_requested\":\n",
    "        print(\"🔀 Routing to regeneration\")\n",
    "        return \"regenerate\"\n",
    "    elif approved:\n",
    "        print(\"🔀 Routing to follow-up task\")\n",
    "        return \"follow_up\"\n",
    "    else:\n",
    "        print(\"🔀 Routing to editing (retry)\")\n",
    "        return \"edit\"\n",
    "\n",
    "\n",
    "def handle_completion(state: DocumentSummaryState) -> DocumentSummaryState:\n",
    "    \"\"\"Handle successful completion of the workflow.\"\"\"\n",
    "    print(\"🎉 Workflow completed successfully!\")\n",
    "    \n",
    "    # Generate completion summary\n",
    "    version_count = len(state.get(\"version_history\", []))\n",
    "    final_summary = state.get(\"human_edited_summary\", \"\")\n",
    "    follow_up_result = state.get(\"follow_up_result\", \"\")\n",
    "    \n",
    "    completion_msg = f\"\"\"Document processing completed successfully!\n",
    "    \n",
    "📊 Summary:\n",
    "- Versions created: {version_count}\n",
    "- Final summary: {len(final_summary.split())} words\n",
    "- Follow-up task completed: {len(follow_up_result.split())} words\n",
    "\n",
    "✅ All tasks completed with human oversight.\"\"\"\n",
    "    \n",
    "    return {\n",
    "        \"messages\": [AIMessage(content=completion_msg)],\n",
    "        \"processing_stage\": \"completed\"\n",
    "    }\n",
    "\n",
    "\n",
    "def handle_error(state: DocumentSummaryState) -> DocumentSummaryState:\n",
    "    \"\"\"Handle errors in the workflow.\"\"\"\n",
    "    print(\"❌ Workflow ended due to error\")\n",
    "    \n",
    "    error_msg = \"\"\"Workflow ended due to an error. Please check:\n",
    "- Document format and content\n",
    "- API connectivity\n",
    "- Input validation\n",
    "\n",
    "You can restart with a new document.\"\"\"\n",
    "    \n",
    "    return {\n",
    "        \"messages\": [AIMessage(content=error_msg)]\n",
    "    }\n",
    "\n",
    "print(\"✅ Routing logic defined\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 5: Build the Graph\n",
    "Construct the LangGraph with all nodes and conditional routing."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def build_document_summary_graph():\n",
    "    \"\"\"Build and compile the document summary editing graph.\"\"\"\n",
    "    print(\"🏗️ Building document summary editing graph...\")\n",
    "    \n",
    "    workflow = StateGraph(DocumentSummaryState)\n",
    "    \n",
    "    # Add nodes\n",
    "    workflow.add_node(\"process_doc\", process_document)\n",
    "    workflow.add_node(\"generate_summary\", generate_ai_summary)\n",
    "    workflow.add_node(\"human_edit\", request_human_editing)\n",
    "    workflow.add_node(\"regenerate\", regenerate_summary)\n",
    "    workflow.add_node(\"follow_up\", execute_follow_up_task)\n",
    "    workflow.add_node(\"complete\", handle_completion)\n",
    "    workflow.add_node(\"error\", handle_error)\n",
    "    \n",
    "    # Set entry point\n",
    "    workflow.set_entry_point(\"process_doc\")\n",
    "    \n",
    "    # Linear flow for initial processing\n",
    "    workflow.add_edge(\"process_doc\", \"generate_summary\")\n",
    "    workflow.add_edge(\"generate_summary\", \"human_edit\")\n",
    "    \n",
    "    # Conditional routing from human editing\n",
    "    workflow.add_conditional_edges(\n",
    "        \"human_edit\",\n",
    "        route_after_editing,\n",
    "        {\n",
    "            \"regenerate\": \"regenerate\",\n",
    "            \"follow_up\": \"follow_up\",\n",
    "            \"edit\": \"human_edit\",  # Retry editing\n",
    "            \"end\": \"error\"\n",
    "        }\n",
    "    )\n",
    "    \n",
    "    # After regeneration, return to editing\n",
    "    workflow.add_edge(\"regenerate\", \"human_edit\")\n",
    "    \n",
    "    # After follow-up task, complete the workflow\n",
    "    workflow.add_edge(\"follow_up\", \"complete\")\n",
    "    \n",
    "    # End nodes\n",
    "    workflow.add_edge(\"complete\", END)\n",
    "    workflow.add_edge(\"error\", END)\n",
    "    \n",
    "    # Compile the graph\n",
    "    graph = workflow.compile()\n",
    "    \n",
    "    print(\"✅ Graph compiled successfully!\")\n",
    "    print(\"   Flow: process_doc → generate_summary → human_edit → [regenerate/follow_up] → complete\")\n",
    "    \n",
    "    return graph\n",
    "\n",
    "# Build the graph\n",
    "document_graph = build_document_summary_graph()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 6: Test the Graph\n",
    "Run the graph with sample documents to demonstrate the editing workflow."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Sample documents for testing\n",
    "SAMPLE_DOCUMENTS = {\n",
    "    \"meeting_notes\": \"\"\"Q3 Strategy Meeting - October 15, 2024\n",
    "\n",
    "Attendees: Sarah Chen (CEO), Mark Rodriguez (CTO), Lisa Wang (COO), David Kim (CFO)\n",
    "\n",
    "Key Discussion Points:\n",
    "1. Product Development Update\n",
    "   - Mobile app beta testing completed with 95% user satisfaction\n",
    "   - Desktop version 2.0 on track for December release\n",
    "   - AI integration features showing promising early results\n",
    "\n",
    "2. Market Expansion\n",
    "   - European market entry planned for Q1 2025\n",
    "   - Partnership discussions with three major distributors ongoing\n",
    "   - Regulatory compliance review initiated\n",
    "\n",
    "3. Financial Performance\n",
    "   - Q3 revenue exceeded targets by 12%\n",
    "   - Operating costs reduced by 8% through efficiency improvements\n",
    "   - Cash flow projections remain positive through 2025\n",
    "\n",
    "4. Team Growth\n",
    "   - Engineering team expansion: 5 new hires planned for Q4\n",
    "   - Sales team restructure to focus on enterprise clients\n",
    "   - Remote work policy updates effective November 1\n",
    "\n",
    "Action Items:\n",
    "- Mark to finalize AI integration timeline by Oct 25\n",
    "- Lisa to complete European market research by Nov 1\n",
    "- David to prepare Q4 budget proposal by Oct 30\n",
    "- Sarah to schedule follow-up with board by Nov 15\n",
    "\n",
    "Next meeting: November 15, 2024\"\"\",\n",
    "\n",
    "    \"research_report\": \"\"\"The Impact of Remote Work on Employee Productivity and Well-being\n",
    "\n",
    "Executive Summary:\n",
    "This study examines the effects of remote work arrangements on employee productivity and mental well-being across 500 knowledge workers from various industries during 2023-2024.\n",
    "\n",
    "Methodology:\n",
    "We conducted surveys, interviews, and performance analysis across companies with different remote work policies: fully remote, hybrid, and traditional office-based.\n",
    "\n",
    "Key Findings:\n",
    "\n",
    "Productivity Metrics:\n",
    "- Remote workers showed 13% higher task completion rates\n",
    "- 25% reduction in time spent in unproductive meetings\n",
    "- 18% increase in deep work sessions (2+ hours uninterrupted)\n",
    "- However, 22% reported difficulty with collaborative tasks\n",
    "\n",
    "Well-being Indicators:\n",
    "- 67% reported improved work-life balance\n",
    "- 31% experienced reduced commute-related stress\n",
    "- 28% reported feelings of isolation\n",
    "- 15% struggled with home-office ergonomics\n",
    "\n",
    "Hybrid Model Results:\n",
    "- 3-2 split (3 days remote, 2 days office) showed optimal results\n",
    "- Maintained collaboration benefits while preserving flexibility\n",
    "- 89% employee satisfaction rate with hybrid arrangement\n",
    "\n",
    "Recommendations:\n",
    "1. Implement structured hybrid policies with clear expectations\n",
    "2. Invest in digital collaboration tools and training\n",
    "3. Provide ergonomic home office stipends\n",
    "4. Schedule regular virtual social interactions\n",
    "5. Establish core collaboration hours for team alignment\n",
    "\n",
    "Conclusion:\n",
    "Remote work can enhance productivity and well-being when properly implemented with appropriate support systems and policies.\"\"\"\n",
    "}\n",
    "\n",
    "def run_document_example(document_key: str = \"meeting_notes\"):\n",
    "    \"\"\"Run the document summary editing example.\"\"\"\n",
    "    print(f\"\\n{'='*70}\")\n",
    "    print(f\"🚀 Running Document Summary Editing Example\")\n",
    "    print(f\"Document: {document_key}\")\n",
    "    print(f\"{'='*70}\")\n",
    "    \n",
    "    if document_key not in SAMPLE_DOCUMENTS:\n",
    "        print(f\"❌ Document '{document_key}' not found. Available: {list(SAMPLE_DOCUMENTS.keys())}\")\n",
    "        return None\n",
    "    \n",
    "    document = SAMPLE_DOCUMENTS[document_key]\n",
    "    \n",
    "    initial_state = {\n",
    "        \"messages\": [HumanMessage(content=document)],\n",
    "        \"original_document\": \"\",\n",
    "        \"ai_summary\": \"\",\n",
    "        \"human_edited_summary\": \"\",\n",
    "        \"summary_approved\": False,\n",
    "        \"editing_notes\": \"\",\n",
    "        \"follow_up_task\": \"\",\n",
    "        \"follow_up_result\": \"\",\n",
    "        \"version_history\": [],\n",
    "        \"processing_stage\": \"initial\"\n",
    "    }\n",
    "    \n",
    "    try:\n",
    "        final_state = None\n",
    "        for state in document_graph.stream(initial_state):\n",
    "            final_state = state\n",
    "        \n",
    "        print(\"\\n\" + \"=\"*70)\n",
    "        print(\"📋 WORKFLOW RESULTS\")\n",
    "        print(\"=\"*70)\n",
    "        \n",
    "        if final_state:\n",
    "            state_values = list(final_state.values())[-1]\n",
    "            \n",
    "            # Show version history\n",
    "            version_history = state_values.get(\"version_history\", [])\n",
    "            if version_history:\n",
    "                print(f\"\\n📚 Version History ({len(version_history)} versions):\")\n",
    "                for i, version in enumerate(version_history, 1):\n",
    "                    print(f\"   {i}. {version['editor']} - {version['word_count']} words - {version['notes']}\")\n",
    "            \n",
    "            # Show final summary\n",
    "            final_summary = state_values.get(\"human_edited_summary\", \"\")\n",
    "            if final_summary:\n",
    "                print(f\"\\n📄 Final Summary:\")\n",
    "                print(\"-\" * 50)\n",
    "                print(final_summary)\n",
    "                print(\"-\" * 50)\n",
    "            \n",
    "            # Show follow-up result\n",
    "            follow_up_result = state_values.get(\"follow_up_result\", \"\")\n",
    "            follow_up_task = state_values.get(\"follow_up_task\", \"\")\n",
    "            if follow_up_result:\n",
    "                print(f\"\\n🎯 Follow-up Task Result:\")\n",
    "                print(f\"Task: {follow_up_task}\")\n",
    "                print(\"-\" * 50)\n",
    "                print(follow_up_result)\n",
    "                print(\"-\" * 50)\n",
    "        \n",
    "        print(\"\\n✅ Document processing workflow completed!\")\n",
    "        return final_state\n",
    "\n",
    "    except Exception as e:\n",
    "        print(f\"❌ Error running example: {e}\")\n",
    "        return None\n",
    "\n",
    "print(\"📝 Document examples ready!\")\n",
    "print(\"\\nAvailable documents:\")\n",
    "for key, doc in SAMPLE_DOCUMENTS.items():\n",
    "    word_count = len(doc.split())\n",
    "    print(f\"   - {key}: {word_count} words\")\n",
    "\n",
    "print(\"\\nTo test the graph, run:\")\n",
    "print('run_document_example(\"meeting_notes\")')\n",
    "print('# or')\n",
    "print('run_document_example(\"research_report\")')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Run an example (uncomment to test)\n",
    "# result = run_document_example(\"meeting_notes\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 7: Unit Tests\n",
    "Test the core functionality of the document processing workflow."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pytest\n",
    "from unittest.mock import patch, MagicMock\n",
    "\n",
    "def test_document_validation():\n",
    "    \"\"\"Test document validation logic.\"\"\"\n",
    "    # Test empty document\n",
    "    is_valid, msg = validate_document(\"\")\n",
    "    assert not is_valid\n",
    "    assert \"empty\" in msg.lower()\n",
    "    \n",
    "    # Test short document\n",
    "    is_valid, msg = validate_document(\"Short doc\")\n",
    "    assert not is_valid\n",
    "    assert \"too short\" in msg.lower()\n",
    "    \n",
    "    # Test valid document\n",
    "    valid_doc = \"This is a valid document with more than ten words to pass validation.\"\n",
    "    is_valid, msg = validate_document(valid_doc)\n",
    "    assert is_valid\n",
    "    assert \"validated\" in msg.lower()\n",
    "    \n",
    "    print(\"✅ test_document_validation passed\")\n",
    "\n",
    "def test_version_history():\n",
    "    \"\"\"Test version history creation.\"\"\"\n",
    "    summary = \"This is a test summary\"\n",
    "    version = create_version_entry(summary, \"AI\", \"Initial version\")\n",
    "    \n",
    "    assert version[\"summary\"] == summary\n",
    "    assert version[\"editor\"] == \"AI\"\n",
    "    assert version[\"notes\"] == \"Initial version\"\n",
    "    assert version[\"word_count\"] == 5\n",
    "    assert \"timestamp\" in version\n",
    "    \n",
    "    print(\"✅ test_version_history passed\")\n",
    "\n",
    "def test_diff_functionality():\n",
    "    \"\"\"Test diff generation between text versions.\"\"\"\n",
    "    original = \"The quick brown fox jumps over the lazy dog.\"\n",
    "    edited = \"The quick brown fox leaps over the lazy dog.\"\n",
    "    \n",
    "    diff = show_diff(original, edited)\n",
    "    assert \"jumps\" in diff\n",
    "    assert \"leaps\" in diff\n",
    "    \n",
    "    # Test no changes\n",
    "    no_diff = show_diff(original, original)\n",
    "    assert no_diff == \"No changes detected.\"\n",
    "    \n",
    "    print(\"✅ test_diff_functionality passed\")\n",
    "\n",
    "def test_summary_generation():\n",
    "    \"\"\"Test AI summary generation with mocking.\"\"\"\n",
    "    state = {\n",
    "        \"original_document\": \"This is a test document for summarization testing.\",\n",
    "        \"version_history\": []\n",
    "    }\n",
    "    \n",
    "    with patch('langchain_openai.ChatOpenAI') as mock_llm_class:\n",
    "        mock_llm = MagicMock()\n",
    "        mock_response = MagicMock()\n",
    "        mock_response.content = \"This is a test summary of the document.\"\n",
    "        mock_llm.invoke.return_value = mock_response\n",
    "        mock_llm_class.return_value = mock_llm\n",
    "        \n",
    "        result = generate_ai_summary(state)\n",
    "        \n",
    "        assert \"ai_summary\" in result\n",
    "        assert result[\"ai_summary\"] == \"This is a test summary of the document.\"\n",
    "        assert result[\"processing_stage\"] == \"summary_generated\"\n",
    "        assert len(result[\"version_history\"]) == 1\n",
    "        \n",
    "    print(\"✅ test_summary_generation passed\")\n",
    "\n",
    "def test_routing_logic():\n",
    "    \"\"\"Test routing decisions based on state.\"\"\"\n",
    "    # Test approved routing\n",
    "    approved_state = {\n",
    "        \"summary_approved\": True,\n",
    "        \"processing_stage\": \"summary_approved\"\n",
    "    }\n",
    "    assert route_after_editing(approved_state) == \"follow_up\"\n",
    "    \n",
    "    # Test regeneration routing\n",
    "    regen_state = {\n",
    "        \"summary_approved\": False,\n",
    "        \"processing_stage\": \"regenerate_requested\"\n",
    "    }\n",
    "    assert route_after_editing(regen_state) == \"regenerate\"\n",
    "    \n",
    "    # Test error routing\n",
    "    error_state = {\n",
    "        \"processing_stage\": \"error\"\n",
    "    }\n",
    "    assert route_after_editing(error_state) == \"end\"\n",
    "    \n",
    "    print(\"✅ test_routing_logic passed\")\n",
    "\n",
    "def test_error_handling():\n",
    "    \"\"\"Test error handling in document processing.\"\"\"\n",
    "    # Test with invalid document\n",
    "    invalid_state = {\"messages\": []}\n",
    "    result = process_document(invalid_state)\n",
    "    \n",
    "    assert \"Error\" in result[\"original_document\"]\n",
    "    assert result[\"processing_stage\"] == \"error\"\n",
    "    \n",
    "    # Test summary generation with error\n",
    "    error_doc_state = {\"original_document\": \"Error: Test error\"}\n",
    "    result = generate_ai_summary(error_doc_state)\n",
    "    \n",
    "    assert \"Cannot summarize\" in result[\"ai_summary\"]\n",
    "    \n",
    "    print(\"✅ test_error_handling passed\")\n",
    "\n",
    "# Run the tests\n",
    "print(\"🧪 Running unit tests for document summary workflow...\")\n",
    "test_document_validation()\n",
    "test_version_history()\n",
    "test_diff_functionality()\n",
    "test_summary_generation()\n",
    "test_routing_logic()\n",
    "test_error_handling()\n",
    "print(\"\\n✅ All unit tests passed!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Explanation\n",
    "\n",
    "### Problem Solved\n",
    "This intermediate example demonstrates a practical human-in-the-loop workflow where:\n",
    "- AI generates an initial document summary\n",
    "- Human reviews and can edit the summary for accuracy\n",
    "- The human-edited summary is used for follow-up tasks\n",
    "- Version history tracks all changes\n",
    "\n",
    "### Graph Structure\n",
    "The workflow follows this enhanced flow:\n",
    "1. **Document Processing**: Validate and prepare the input document\n",
    "2. **AI Summary Generation**: Create initial summary using GPT-4\n",
    "3. **Human Editing Interface**: Present summary for human review/editing\n",
    "4. **Conditional Flow**: Based on human decision (approve/edit/regenerate)\n",
    "5. **Follow-up Task**: Use approved summary for additional AI tasks\n",
    "6. **Completion**: Finalize with results and statistics\n",
    "\n",
    "### Human Intervention Points\n",
    "The human can intervene at multiple points:\n",
    "- **Approval**: Accept the AI summary as-is\n",
    "- **Editing**: Directly modify the summary text\n",
    "- **Regeneration**: Request AI to improve based on feedback\n",
    "- **Follow-up Selection**: Choose what task to perform with the summary\n",
    "\n",
    "### Graph Adaptation\n",
    "The graph adapts to human input by:\n",
    "- **Version Tracking**: Maintaining history of all summary versions\n",
    "- **Feedback Integration**: Using human feedback to regenerate improved summaries\n",
    "- **Flexible Routing**: Supporting multiple editing cycles until approval\n",
    "- **Task Customization**: Allowing human choice of follow-up tasks\n",
    "\n",
    "### Advanced Features\n",
    "- **Diff Visualization**: Shows exact changes between AI and human versions\n",
    "- **Multi-modal Editing**: Support for approval, direct editing, or regeneration\n",
    "- **Version History**: Complete audit trail of all changes\n",
    "- **Error Recovery**: Graceful handling of processing errors\n",
    "- **Flexible Follow-up**: Multiple follow-up task options based on the refined summary\n",
    "\n",
    "This example demonstrates how human expertise can be seamlessly integrated into AI workflows to improve quality and accuracy of the final output."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}